#include "Shader.h"
#include <stb_include.h>
#include "core/CElapsedTimer.h"
#include "platform/PlatUtil.h"

CMap<CString, GLuint> Shader::ShaderProgramMap;

/**@note when start in a new thread, gl context changes, must recreate all gl objects!*/
void Shader::install(const CString &shaderName) {
	// same shaderName should linking same shader program, not for every Entity, avoid abuse!
	if (ShaderProgramMap.contains(shaderName)) {
		m_progID = ShaderProgramMap[shaderName];
		m_shaderName = shaderName;
		return;
	}
	static CElapsedTimer timer;
	timer.reset();

	const auto& VertexPath = Config::shaderPath(ShaderType::VertexShader, shaderName);
	const auto& FragmentPath = Config::shaderPath(ShaderType::FragmentShader, shaderName);
	const auto& GeometryPath = Config::shaderPath(ShaderType::GeometryShader, shaderName);

	// 1. retrieve the vertex/fragment source code from filePath
	CString vertexCode, fragmentCode, geometryCode;
	try {
		// open files
		PlatUtil::readFileAsString(vertexCode, VertexPath);
		PlatUtil::readFileAsString(fragmentCode, FragmentPath);
		if (!GeometryPath.empty())
			PlatUtil::readFileAsString(geometryCode, GeometryPath);
	} catch (const std::exception &e) {
		LogE << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ:" << e.what();
	}

	processIncludes(vertexCode, fragmentCode, geometryCode);
	
	const char* vShaderCode = vertexCode.data();
	const char* fShaderCode = fragmentCode.data();
	const char* gShaderCode = geometryCode.data();

	// 2. compile shaders
	// vertex shader
	GLuint vertex = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex, 1, &vShaderCode, NULL);
	glCompileShader(vertex);
	checkCompileErrors(vertex, TYPE_VERTEX, shaderName);
	// fragment Shader
	GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment, 1, &fShaderCode, NULL);
	glCompileShader(fragment);
	checkCompileErrors(fragment, TYPE_FRAGMENT, shaderName);
	// if geometry shader is given, compile geometry shader
	GLuint geometry;
	if (!GeometryPath.empty()) {
		geometry = glCreateShader(GL_GEOMETRY_SHADER);
		glShaderSource(geometry, 1, &gShaderCode, NULL);
		glCompileShader(geometry);
		checkCompileErrors(geometry, TYPE_GEOMETRY, shaderName);
	}
	// shader Program
	m_progID = glCreateProgram();
	m_shaderName = shaderName;

	glAttachShader(m_progID, vertex);
	glAttachShader(m_progID, fragment);
	if (!GeometryPath.empty())
		glAttachShader(m_progID, geometry);
	/** The linking process combines various shader stages (such as vertex shaders and fragment shaders) 
	 *  and generates the final binary code, so that the GPU can execute it. The stage spend most time.
	 */
	glLinkProgram(m_progID);
	checkCompileErrors(m_progID, TYPE_PROGRAM, shaderName);

	// delete the shaders as they're linked into our program now and no longer necessary
	glDeleteShader(vertex);
	glDeleteShader(fragment);
	if (!GeometryPath.empty())
		glDeleteShader(geometry);
	ShaderProgramMap.insert(shaderName, m_progID);

	LogI << "Shader::" << shaderName << " gl program has created, " << " [took " << timer.elapsed() << "ms]";
}

void Shader::processIncludes(std::string& vertexCode, std::string& fragmentCode, std::string& geometryCode) {
	#ifdef __ANDROID__
		stb_initAndroid(AAssetMgr::get()->rawPtr());
	#endif
	CUtil::replaceIncludes(vertexCode, Config::Shader_Dir);
	CUtil::replaceIncludes(fragmentCode, Config::Shader_Dir);
	if (!geometryCode.empty())
		CUtil::replaceIncludes(geometryCode, Config::Shader_Dir);
}